package com.titanrise.crm.common.utils;

import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;

public class ListPipeUtil {
    public static <A, B> Function<List<A>, B> foldLeft(BiFunction<B, A, B> fn, B initVal) {
        return (list) -> {
            B result = initVal;

            for (A a : list)
                result = fn.apply(result, a);

            return result;
        };
    }

    public static <A, B> Function<List<A>, B> foldRight(BiFunction<A, B, B> fn, B initVal) {
        return (list) -> {
            B result = initVal;

            for (int i = list.size() - 1; i >= 0; i--)
                result = fn.apply(list.get(i), result);

            return result;
        };
    }

    public static <A, B> Function<List<A>, List<B>> map(Function<A, B> mapper) {
        return (list) -> {
            List<B> result = new ArrayList<>();

            for (A a : list)
                result.add(mapper.apply(a));

            return result;
        };
    }

    public static <A, B> Function<List<A>, List<B>> mapWithIndex(BiFunction<A, Integer, B> mapper) {
        return (list) -> {
            List<B> result = new ArrayList<>();

            for (int i = 0; i < list.size(); i++)
                result.add(mapper.apply(list.get(i), i));

            return result;
        };
    }

    public static <A> Function<List<A>, List<A>> forEach(Consumer<A> consumer) {
        return (list) -> {
            for (A a : list)
                consumer.accept(a);
            return list;
        };
    }

    public static <A> Function<List<A>, List<A>> forEachWithIndex(BiConsumer<A, Integer> consumer) {
        return (list) -> {
            for (int i = 0; i < list.size(); i++)
                consumer.accept(list.get(i), i);
            return list;
        };
    }

    public static <A, B> Function<List<A>, List<B>> flatMap(Function<A, List<B>> fn) {
        return (list) -> {
            List<B> result = new ArrayList<>();

            for (A a : list)
                result.addAll(fn.apply(a));

            return result;
        };
    }

    public static <A> Function<List<A>, List<A>> filter(Predicate<A> predicate) {
        return (list) -> {
            List<A> result = new ArrayList<>();

            for (A a : list)
                if (predicate.test(a))
                    result.add(a);

            return result;
        };
    }

    public static <A> Function<List<A>, Pair<List<A>, List<A>>> partition(Predicate<A> predicate) {
        return (list) -> {
            List<A> l1 = new ArrayList<>();
            List<A> l2 = new ArrayList<>();

            for (A a : list)
                if (predicate.test(a))
                    l1.add(a);
                else
                    l2.add(a);

            return Pair.of(l1, l2);
        };
    }

    public static <A, B> List<Pair<A, B>> zip(Pair<List<A>, List<B>> listPair) {
        List<A> listA = listPair.getFirst();
        List<B> listB = listPair.getSecond();
        List<Pair<A, B>> result = new ArrayList<>();

        for (int i = 0; i < Math.min(listA.size(), listB.size()); i++)
            result.add(Pair.of(listA.get(i), listB.get(i)));

        return result;
    }

    public static <A, B> Pair<List<A>, List<B>> unzip(List<Pair<A, B>> pairList) {
        List<A> listA = new ArrayList<>();
        List<B> listB = new ArrayList<>();

        for (Pair<A, B> pair : pairList) {
            listA.add(pair.getFirst());
            listB.add(pair.getSecond());
        }

        return Pair.of(listA, listB);
    }

    public static <A, B, C> Function<Pair<List<A>, List<B>>, List<C>> zipWith(BiFunction<A, B, C> fn) {
        return Functions.flow(
                ListPipeUtil::zip,
                ListPipeUtil.map(pair -> fn.apply(pair.getFirst(), pair.getSecond()))
        );
    }

    public static <A> Maybe<A> head(List<A> list) {
        return list.isEmpty() ? Maybe.nothing() : Maybe.just(list.get(0));
    }

    public static <A> Function<List<A>, List<A>> toSliced(int start, int end) {
        return (list) -> {
            List<A> result = new ArrayList<>();
            for (int i = start; i < list.size() && i < end; i++)
                result.add(list.get(i));
            return result;
        };
    }

    public static <A> List<A> toReversed(List<A> list) {
        List<A> result = new ArrayList<>();
        for (int i = list.size() - 1; i >= 0; i--)
            result.add(list.get(i));
        return result;
    }

    public static <A> Function<List<A>, List<A>> toSorted(Comparator<A> comparator) {
        return (list) -> list.stream().sorted(comparator).collect(Collectors.toList());
    }
}
